package site.mingdao.boot.web.plugin.log.core.aop;

import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import site.mingdao.boot.common.biz.log.OperateLogCommonApi;
import site.mingdao.boot.common.biz.log.dto.OperateLogDTO;
import site.mingdao.boot.common.exception.enums.GlobalErrorCodeConstants;
import site.mingdao.boot.common.util.servlet.ServletUtils;
import site.mingdao.boot.web.core.util.WebFrameworkUtils;
import site.mingdao.boot.web.plugin.log.core.annotation.OperateLog;
import site.mingdao.boot.web.plugin.log.core.enums.OperateTypeEnum;

import java.lang.reflect.Method;

import static site.mingdao.boot.common.util.json.JsonUtils.toJsonString;

@Aspect
public class OperateLogAspect {

    @Resource
    private OperateLogCommonApi operateLogCommonApi;

    @Pointcut("@annotation(site.mingdao.boot.web.plugin.log.core.annotation.OperateLog)")
    private void pointcut() {
    }

    @AfterReturning(pointcut = "pointcut()", returning = "result")
    public void doAfterReturning(JoinPoint joinPoint, Object result) {
        OperateLogDTO operationLogDTO = buildOperateLog(joinPoint);
        operationLogDTO.setResultCode(GlobalErrorCodeConstants.SUCCESS.getCode());
        operationLogDTO.setResultMsg(toJsonString(result));
        operateLogCommonApi.createOperateLogAsync(operationLogDTO);
    }

    @AfterThrowing(pointcut = "pointcut()", throwing = "exception")
    public void doAfterThrowing(JoinPoint joinPoint, Exception exception) {
        OperateLogDTO operationLogDTO = buildOperateLog(joinPoint);
        operationLogDTO.setResultCode(GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR.getCode());
        StackTraceElement[] stackTrace = exception.getStackTrace();
        if (stackTrace.length > 0) {
            String message = "Error occurred at: " + stackTrace[0].getClassName() +
                    "(" + stackTrace[0].getFileName() + ":" + stackTrace[0].getLineNumber() + ")";
            operationLogDTO.setResultMsg(message);
        }
        operateLogCommonApi.createOperateLogAsync(operationLogDTO);
    }

    private OperateLogDTO buildOperateLog(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes == null) {
            throw new IllegalStateException("RequestAttributes not exist!");
        }
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        OperateLog annotation = method.getAnnotation(OperateLog.class);
        HttpServletRequest request = attributes.getRequest();
        OperateLogDTO operationLogDTO = new OperateLogDTO();
        Boolean requestEnable = annotation.requestEnable();
        if (BooleanUtil.isTrue(requestEnable)) {
            operationLogDTO.setRequestParams(toJsonString(ServletUtils.getParamMap(request)));
        }
        String operateModule = annotation.operateModule();
        if (StrUtil.isBlank(operateModule)) {
            Tag tag = method.getDeclaringClass().getAnnotation(Tag.class);
            operateModule = tag != null ? StrUtil.nullToDefault(tag.name(), tag.description()) : null;
            operationLogDTO.setOperateModule(operateModule);
        }
        String operateName = annotation.operateName();
        if (StrUtil.isBlank(operateName)) {
            Operation Operation = method.getAnnotation(Operation.class);
            operateName = Operation != null ? Operation.summary() : null;
            operationLogDTO.setOperateName(operateName);
        }
        OperateTypeEnum[] operateTypeEnums = annotation.operateType();
        if (operateTypeEnums.length > 0) {
            operationLogDTO.setOperateType(operateTypeEnums[0].getType());
        } else {
            operationLogDTO.setOperateType(OperateTypeEnum.OTHER.getType());
        }
        operationLogDTO
                .setUserId(WebFrameworkUtils.getLoginUserId(request))
                .setUserType(WebFrameworkUtils.getLoginUserType(request))
                .setRequestMethod(request.getMethod())
                .setRequestUrl(request.getRequestURI())
                .setUserIp(ServletUtils.getClientIP(request))
                .setUserAgent(ServletUtils.getUserAgent(request))
        ;
        return operationLogDTO;
    }


}
